package com.closer.service.biz.app.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.closer.service.biz.ai.core.AiCodeGeneratorFacade;
import com.closer.service.biz.ai.core.builder.VueProjectBuilder;
import com.closer.service.biz.ai.core.handler.StreamHandlerExecutor;
import com.closer.service.biz.ai.enums.CodeGenTypeEnum;
import com.closer.service.biz.ai.factory.AiCodeGenTypeRoutingServiceFactory;
import com.closer.service.biz.app.dto.AppAddRequest;
import com.closer.service.biz.app.dto.AppAdminUpdateRequest;
import com.closer.service.biz.app.dto.AppQueryRequest;
import com.closer.service.biz.app.dto.AppUpdateRequest;
import com.closer.service.biz.app.dto.vo.AppVO;
import com.closer.service.biz.app.validator.AppValidator;
import com.closer.service.biz.chatHistory.enums.ChatHistoryMessageTypeEnum;
import com.closer.service.biz.chatHistory.service.ChatHistoryService;
import com.closer.service.biz.cos.service.ProjectDownLoadService;
import com.closer.service.biz.cos.service.ScreenshotService;
import com.closer.service.biz.user.dto.vo.LoginUserVO;
import com.closer.service.biz.user.dto.vo.UserVO;
import com.closer.service.biz.user.entity.User;
import com.closer.service.biz.user.service.UserService;
import com.closer.service.support.common.DeleteRequest;
import com.closer.service.support.constant.AppConstant;
import com.closer.service.support.exception.BusinessException;
import com.closer.service.support.exception.ErrorCode;
import com.closer.service.support.exception.ThrowUtils;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.closer.service.biz.app.entity.App;
import com.closer.service.repository.AppMapper;
import com.closer.service.biz.app.service.AppService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.io.File;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 应用 服务层实现。
 *
 * @author closer
 */
@Service
@Slf4j
public class AppServiceImpl extends ServiceImpl<AppMapper, App>  implements AppService{

    @Value("${code.deploy-host:http://localhost}")
    private String deployHost;

    private final AppValidator validator;

    private final UserService userService;

    private final AiCodeGeneratorFacade genCode;

    private final ChatHistoryService chatHistoryService;

    private final StreamHandlerExecutor streamHandlerExecutor;

    private final VueProjectBuilder vueProjectBuilder;

    private final ScreenshotService screenshotService;

    private final ProjectDownLoadService projectDownLoadService;

    private final AiCodeGenTypeRoutingServiceFactory aiCodeGenTypeRoutingServiceFactory;

    public AppServiceImpl(AppValidator validator, UserService userService, AiCodeGeneratorFacade genCode,
                          ChatHistoryService chatHistoryService, StreamHandlerExecutor streamHandlerExecutor,
                          VueProjectBuilder vueProjectBuilder, ScreenshotService screenshotService, ProjectDownLoadService projectDownLoadService,
                          AiCodeGenTypeRoutingServiceFactory aiCodeGenTypeRoutingServiceFactory) {
        this.validator = validator;
        this.userService = userService;
        this.genCode = genCode;
        this.chatHistoryService = chatHistoryService;
        this.streamHandlerExecutor = streamHandlerExecutor;
        this.vueProjectBuilder = vueProjectBuilder;
        this.screenshotService = screenshotService;
        this.projectDownLoadService = projectDownLoadService;
        this.aiCodeGenTypeRoutingServiceFactory = aiCodeGenTypeRoutingServiceFactory;
    }


    @Override
    public long addApp(AppAddRequest params, LoginUserVO loginUser) {

        var initPrompt = params.getInitPrompt();
        ThrowUtils.throwIf(StrUtil.isBlank(initPrompt), ErrorCode.PARAMS_ERROR, "初始化 prompt 不能为空");
        // 构造入库对象
        var app = new App();
        BeanUtil.copyProperties(params, app);
        app.setUserId(loginUser.getId());
        // 应用名称暂时为 initPrompt 前 12 位
        app.setAppName(initPrompt.substring(0, Math.min(initPrompt.length(), 12)));
        // 使用 AI 智能选择代码生成类型（多例模式）
        var aiCodeGenTypeRoutingService = aiCodeGenTypeRoutingServiceFactory.createAiCodeGenTypeRoutingService();
        var selectedCodeGenType = aiCodeGenTypeRoutingService.routeCodeGenType(initPrompt);
        app.setCodeGenType(selectedCodeGenType.getValue());
        // 插入数据库
        boolean result = this.save(app);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        log.info("应用创建成功，ID: {}, 类型: {}", app.getId(), selectedCodeGenType.getValue());
        return app.getId();
    }

    @Override
    public boolean updateApp(AppUpdateRequest params, LoginUserVO loginUser) {
        var oldApp = this.getById(params.getId());
        validator.validateUpdate(params,loginUser.getId(),oldApp);
        var app = new App();
        app.setId(params.getId());
        app.setAppName(params.getAppName());
        //设置编辑时间
        app.setEditTime(LocalDateTime.now());
        var result = this.updateById(app);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        return true;
    }

    @Override
    public boolean delete(DeleteRequest params, LoginUserVO loginUser) {
        var oldApp = this.getById(params.getId());
        validator.validateDelete(params,loginUser,oldApp);
        return this.removeById(params.getId());
    }

    @Override
    public boolean removeById(Serializable id){
        if(Objects.isNull(id)){
            return false;
        }
        var appId = Long.valueOf( id.toString());
        if (appId <= 0) {
            return false;
        }
        try {
            chatHistoryService.deleteByAppId(appId);
        } catch (Exception e){
            log.error("删除应用时，删除聊天记录失败:{}",e.getMessage());
        }
        return super.removeById(id);
    }

    @Override
    public AppVO getAppVO(Long id) {
        ThrowUtils.throwIf(id<=0, ErrorCode.PARAMS_ERROR);
        var app = this.getById(id);
        ThrowUtils.throwIf(app==null, ErrorCode.NOT_FOUND_ERROR);
        var appVO = new AppVO();
        BeanUtil.copyProperties(app, appVO);
        // 关联查询用户信息
        var userId = app.getUserId();
        if (userId != null) {
            var user = userService.getById(userId);
            var userVO = userService.getUserVO(user);
            appVO.setUser(userVO);
        }
        return appVO;
    }



    @Override
    public QueryWrapper getQueryWrapper(AppQueryRequest appQueryRequest) {
        if (appQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
        }
        var id = appQueryRequest.getId();
        var appName = appQueryRequest.getAppName();
        var cover = appQueryRequest.getCover();
        var initPrompt = appQueryRequest.getInitPrompt();
        var codeGenType = appQueryRequest.getCodeGenType();
        var deployKey = appQueryRequest.getDeployKey();
        var priority = appQueryRequest.getPriority();
        var userId = appQueryRequest.getUserId();
        var sortField = appQueryRequest.getSortField();
        var sortOrder = appQueryRequest.getSortOrder();
        return QueryWrapper.create()
                .eq("id", id)
                .like("appName", appName)
                .like("cover", cover)
                .like("initPrompt", initPrompt)
                .eq("codeGenType", codeGenType)
                .eq("deployKey", deployKey)
                .eq("priority", priority)
                .eq("userId", userId)
                .orderBy(sortField, "ascend".equals(sortOrder));
    }

    @Override
    public List<AppVO> getAppVOList(List<App> appList) {
        if (CollUtil.isEmpty(appList)) {
            return new ArrayList<>();
        }
        // 批量获取用户信息，避免 N+1 查询问题
        var userIds = appList.stream()
                .map(App::getUserId)
                .collect(Collectors.toSet());
        var userVOMap = userService.listByIds(userIds).stream()
                .collect(Collectors.toMap(User::getId, userService::getUserVO));
        return appList.stream().map(app -> {
            AppVO appVO = getAppVO(app.getId());
            UserVO userVO = userVOMap.get(app.getUserId());
            appVO.setUser(userVO);
            return appVO;
        }).collect(Collectors.toList());
    }

    @Override
    public Page<AppVO> getMyAppVOByPage(AppQueryRequest params, LoginUserVO loginUser) {
        ThrowUtils.throwIf(params == null, ErrorCode.PARAMS_ERROR);
        //限制每页最大 20 条
        var pageSize = params.getPageSize();
        ThrowUtils.throwIf(params.getPageSize() > 20, ErrorCode.PARAMS_ERROR, "每页最多支持 20 个应用");
        var pageNum = params.getPageNum();
        // 只查询当前用户的应用
        params.setUserId(loginUser.getId());
        var queryWrapper = getQueryWrapper(params);
        var appPage = this.page(Page.of(pageNum, pageSize), queryWrapper);
        Page<AppVO> appVOPage = new Page<>(pageNum, pageSize, appPage.getTotalRow());
        var appVOList = getAppVOList(appPage.getRecords());
        appVOPage.setRecords(appVOList);
        return appVOPage;
    }

    @Override
    public Page<AppVO> getGoodAppVOByPage(AppQueryRequest params) {
        ThrowUtils.throwIf(params == null, ErrorCode.PARAMS_ERROR);
        //限制每页最大 20 条
        var pageSize = params.getPageSize();
        ThrowUtils.throwIf(params.getPageSize() > 20, ErrorCode.PARAMS_ERROR, "每页最多支持 20 个应用");
        var pageNum = params.getPageNum();
        // 只查询精选的应用
        params.setPriority(AppConstant.GOOD_APP_PRIORITY);
        var queryWrapper = getQueryWrapper(params);
        //分页查询
        var appPage = this.page(Page.of(pageNum, pageSize), queryWrapper);
        //数据封装
        Page<AppVO> appVOPage = new Page<>(pageNum, pageSize, appPage.getTotalRow());
        var appVOList = getAppVOList(appPage.getRecords());
        appVOPage.setRecords(appVOList);
        return appVOPage;
    }

    @Override
    public boolean deleteByAdmin(DeleteRequest params) {
        if (params == null || params.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        var id = params.getId();
        // 判断是否存在
        var oldApp = this.getById(id);
        ThrowUtils.throwIf(oldApp == null, ErrorCode.NOT_FOUND_ERROR);
        return this.removeById(id);
    }

    @Override
    public boolean updateByAdmin(AppAdminUpdateRequest params) {
        if (params == null || params.getId() == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        long id = params.getId();
        // 判断是否存在
        App oldApp = this.getById(id);
        ThrowUtils.throwIf(oldApp == null, ErrorCode.NOT_FOUND_ERROR);
        App app = new App();
        BeanUtil.copyProperties(params, app);
        // 设置编辑时间
        app.setEditTime(LocalDateTime.now());
        boolean result = this.updateById(app);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        return true;
    }

    @Override
    public Page<AppVO> listAppVOByPageByAdmin(AppQueryRequest params) {
        ThrowUtils.throwIf(params == null, ErrorCode.PARAMS_ERROR);
        var pageNum = params.getPageNum();
        var pageSize = params.getPageSize();
        var queryWrapper = this.getQueryWrapper(params);
        var appPage = this.page(Page.of(pageNum, pageSize), queryWrapper);
        // 数据封装
        Page<AppVO> appVOPage = new Page<>(pageNum, pageSize, appPage.getTotalRow());
        var appVOList = this.getAppVOList(appPage.getRecords());
        appVOPage.setRecords(appVOList);
        return appVOPage;
    }

    @Override
    public Flux<String> chatToGenCode(Long appId, String message, LoginUserVO loginUser){
        ThrowUtils.throwIf(appId == null || appId <= 0, ErrorCode.PARAMS_ERROR, "应用 ID 不能为空");
        ThrowUtils.throwIf(StrUtil.isBlank(message), ErrorCode.PARAMS_ERROR, "用户消息不能为空");
        var app = this.getById(appId);
        if (!app.getUserId().equals(loginUser.getId())){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR,"无权限访问该应用");
        }
        //获取应用的代码生成类型
        var codeGenType = app.getCodeGenType();
        var codeGenTypeEnum = CodeGenTypeEnum.getEnumByValue(codeGenType);
        if (codeGenTypeEnum == null){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR,"不支持的代码生成类型");
        }
        //通过校验后，添加用户消息到对话历史
        chatHistoryService.addChatMessage(appId,message, ChatHistoryMessageTypeEnum.USER.getValue(),loginUser.getId());
        //调用AI生成代码（流式）
        var codeStream = genCode.generateAndSaveCodeStream(message,codeGenTypeEnum,appId);
        // 收集AI响应内容并在完成后记录到对话历史
        return streamHandlerExecutor.doExecute(codeStream,chatHistoryService,appId,loginUser,codeGenTypeEnum);
    }

    @Override
    public String deployApp(Long appId, LoginUserVO loginUser) {
        //参数校验
        ThrowUtils.throwIf(appId == null || appId <= 0, ErrorCode.PARAMS_ERROR, "应用 ID 不能为空");
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR, "用户未登录");
        //2.查询应用信息
        var app = this.getById(appId);
        //3.验证用户是否有权限部署该应用，仅本人可以部署
        if(!app.getUserId().equals(loginUser.getId())){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR,"无权限部署该应用");
        }
        //4.检查是否已经有deployKey
        var deployKey = app.getDeployKey();
        //没有则生成6位deployKey（大小写字母+数字）
        if(StrUtil.isBlank(deployKey)){
            deployKey = RandomUtil.randomString(6);
        }
        //5.获取代码生成类型，构建源目录路径
        var codeGenType = app.getCodeGenType();
        var sourceDirName = codeGenType + "_" + appId;
        var sourceDirPath = AppConstant.CODE_OUTPUT_ROOT_DIR + File.separator + sourceDirName;
        //6.检查源目录是否存在
        var sourceDir = new File(sourceDirPath);
        if(!sourceDir.exists() || !sourceDir.isDirectory()){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR,"应用代码不存在，请先生成代码");
        }
        //7.Vue项目特殊处理：执行构建
        var codeGenTypeEnum = CodeGenTypeEnum.getEnumByValue(codeGenType);
        if(codeGenTypeEnum == CodeGenTypeEnum.VUE_PROJECT){
            //vue项目需要构建
            var buildSuccess = vueProjectBuilder.buildProject(sourceDirPath);
            ThrowUtils.throwIf(!buildSuccess,ErrorCode.SYSTEM_ERROR,"Vue项目构建失败，请重试");
            // 检查dist目录是否存在
            var distDir = new File(sourceDirPath,"dist");
            ThrowUtils.throwIf(!distDir.exists(),ErrorCode.SYSTEM_ERROR,"Vue项目构建完成但未生成dist目录");
            //构建完成后，需要将构建后的文件复制到部署目录
            sourceDir = distDir;
        }
        //8.复制文件到部署目录
        var deployDirPath = AppConstant.CODE_DEPLOY_ROOT_DIR + File.separator + deployKey;
        try {
            FileUtil.copyContent(sourceDir, new File(deployDirPath), true);
        } catch (Exception e){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "部署失败：" + e.getMessage());
        }
        // 9. 更新应用的 deployKey 和部署时间
        var updateApp = new App();
        updateApp.setId(appId);
        updateApp.setDeployKey(deployKey);
        updateApp.setDeployedTime(LocalDateTime.now());
        var updateResult = this.updateById(updateApp);
        ThrowUtils.throwIf(!updateResult, ErrorCode.OPERATION_ERROR, "更新应用部署信息失败");
        // 10. 构建应用访问 URL
        var appDeployUrl = String.format("%s/%s/", deployHost, deployKey);
        //11.异步生成截图并更新封面
        generateAppScreenshotAsync(appId,appDeployUrl);
       return appDeployUrl;
    }

    @Override
    public void generateAppScreenshotAsync(Long appId, String appUrl) {
        // 使用虚拟线程异步执行
        Thread.startVirtualThread(() -> {
            // 调用截图服务生成截图并上传
            var screenshotUrl = screenshotService.generateAndUploadScreenshot(appUrl);
            // 更新应用封面字段
            var updateApp = new App();
            updateApp.setId(appId);
            updateApp.setCover(screenshotUrl);
            var updated = this.updateById(updateApp);
            ThrowUtils.throwIf(!updated, ErrorCode.OPERATION_ERROR, "更新应用封面字段失败");
        });
    }

    @Override
    public void downloadAppCode(Long appId, LoginUserVO loginUser, HttpServletResponse response) {
        ThrowUtils.throwIf(appId == null || appId <= 0, ErrorCode.PARAMS_ERROR, "应用 ID 不能为空");
        var app = this.getById(appId);
        ThrowUtils.throwIf(app == null, ErrorCode.NOT_FOUND_ERROR, "应用不存在");
        //权限校验
        if(!app.getUserId().equals(loginUser.getId())){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR,"无权限下载该应用代码");
        }
        //构建应用代码目录路径（生成目录，非部署目录）
        var codeGenType = app.getCodeGenType();
        var sourceDirName = codeGenType + "_" + appId;
        var sourceDirPath = AppConstant.CODE_OUTPUT_ROOT_DIR + File.separator + sourceDirName;
        var sourceDir = new File(sourceDirPath);
        ThrowUtils.throwIf(!sourceDir.exists() || !sourceDir.isDirectory(), ErrorCode.NOT_FOUND_ERROR, "应用代码不存在，请先生成代码");
        var downloadFileName = String.valueOf(appId);
        projectDownLoadService.downloadProjectAsZip(sourceDirPath, downloadFileName, response);

    }


}
